package com.liuyi.netty.im.commons.codes;

import cn.hutool.core.util.EnumUtil;
import com.liuyi.netty.im.commons.codes.serialize.ContentTypeSerializeManage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.TooLongFrameException;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * ImCode 解码器
 *
 * @author Mr.Fmy
 * @since 2021-11-23
 */
@Slf4j
public class LimCodeDecoder extends ByteToMessageDecoder {

    private final int maxBytesInMessage;

    public LimCodeDecoder() {
        this(8092);
    }

    public LimCodeDecoder(int maxBytesInMessage) {
        this.maxBytesInMessage = maxBytesInMessage;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception {
        Object decoded = this.decode(ctx, byteBuf);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    private Object decode(ChannelHandlerContext ctx, ByteBuf byteBuf) {
        //丢弃已读字节，主要为了防止包扩大
        byteBuf.discardReadBytes();
        if (byteBuf.readableBytes() < 4) {
            return null;
        }
        int beginIndex;
        int magicNumber;
        while (true) {
            //循环读取魔数
            beginIndex = byteBuf.readerIndex();
            magicNumber = byteBuf.readInt();
            if (magicNumber == CodeConst.MAGIC_NUMBER) {
                //验证魔数成功，跳出循环
                break;
            }
            //如果可读取长度为0，则证明该消息为无效消息
            if (byteBuf.readableBytes() == 0) {
                return null;
            }
        }

        if (byteBuf.readableBytes() < 1) {
            //未发现版本号，还原到魔数开始位置
            byteBuf.readerIndex(beginIndex);
            return null;
        }
        //验证版本号
        byte version = byteBuf.readByte();
        if (!(version >= CodeConst.MIN_VERSION && version < CodeConst.MAX_VERSION)) {
            log.debug("Current Version （>=" + CodeConst.MIN_VERSION + "<" + CodeConst.MAX_VERSION + "） Request Version：" + version);
            return null;
        }
        //数据类型
        if (byteBuf.readableBytes() < 1) {
            //未发现数据类型，还原到魔数开始位置
            byteBuf.readerIndex(beginIndex);
            return null;
        }
        byte contentType = byteBuf.readByte();
        //指令
        if (byteBuf.readableBytes() < 1) {
            //未发现指令，还原到魔数开始位置
            byteBuf.readerIndex(beginIndex);
            return null;
        }
        byte com = byteBuf.readByte();
        CodeConst.Command command = EnumUtil.likeValueOf(CodeConst.Command.class, com);
        if (command == null) {
            //指令错误，丢弃该组数据
            log.debug("Command Error " + com);
            return null;
        }
        //长度
        if (byteBuf.readableBytes() < 4) {
            //未发现数据长度，还原到魔数开始位置
            byteBuf.readerIndex(beginIndex);
            return null;
        }
        int length = byteBuf.readInt();

        if (byteBuf.readableBytes() < length) {
            //数据长度不足，还原到魔数开始位置
            byteBuf.readerIndex(beginIndex);
            return null;
        }
        //读取指定长度
        byte[] data = null;
        if (length > 0) {
            if (length > maxBytesInMessage) {
                throw new TooLongFrameException("too large message: " + length + " bytes");
            }
            data = new byte[length];
            byteBuf.readBytes(data);
        }
        LimProtocol message = new LimProtocol();
        message.setVersion(version);
        message.setDataType(contentType);
        message.setCommand(command);
        message.setDataLength(length);
        if (data != null) {
            message.setData(ContentTypeSerializeManage.get(contentType).deserialization(data, command.gettClass()));
        }
        return message;
    }
}
